package com.iwdnb.gkgz.common.quota;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import com.alibaba.fastjson.annotation.JSONField;

import com.iwdnb.bmnf.common.utils.BeanConvertUtils;
import com.iwdnb.bmnf.common.utils.BooleanUtils;
import com.iwdnb.gkgz.common.model.dto.StockDayData;
import com.iwdnb.gkgz.common.utils.BigDecimalUtils;
import com.iwdnb.gkgz.common.utils.DateUtils;
import com.iwdnb.gkgz.common.utils.StockUtils;
import com.iwdnb.gkgz.common.utils.WorkDayUtils;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

public class BiasIndicator {

    public static final String QUOTA_NAME = "bias";
    private static final Integer PERIOD = 30;
    private static final Integer LONG_PERIOD = 60;

    public static List<BigDecimal> calculateBias(List<StockDayData> stockDayDataList) {
        return calculateBias(stockDayDataList, PERIOD, LONG_PERIOD);
    }

    public static List<BigDecimal> calculateBias(List<StockDayData> stockDayDataList, int period, int longPeriod) {
        List<BigDecimal> list = doCalculateBias(stockDayDataList, period);
        List<BigDecimal> longList = doCalculateBias(stockDayDataList, longPeriod);

        int size = stockDayDataList.size();
        for (int i = 0; i < size; i++) {
            StockDayData stockDayData = stockDayDataList.get(i);

            BigDecimal bias = list.get(i);
            BigDecimal longBias = longList.get(i);
            stockDayData.setBias(new Bias(stockDayData.getDatetime(), bias, longBias, stockDayData.getClosePrice()));

        }
        return list;
    }

    public static List<BigDecimal> doCalculateBias(List<StockDayData> stockDayDataList, int period) {
        List<BigDecimal> deviationRates = new ArrayList<>();

        for (int i = 0; i < stockDayDataList.size(); i++) {
            if (i < period - 1) {
                // 不足MA周期的数据时，偏离率为null
                deviationRates.add(BigDecimal.ZERO);
                continue;
            }

            // 计算移动平均
            BigDecimal sum = BigDecimal.ZERO;
            for (int j = i - period + 1; j <= i; j++) {
                sum = sum.add(stockDayDataList.get(j).getClosePrice());
            }
            BigDecimal movingAverage = BigDecimalUtils.divide(sum, BigDecimal.valueOf(period));

            // 当前收盘价
            BigDecimal currentClosePrice = stockDayDataList.get(i).getClosePrice();

            // 计算偏离率
            BigDecimal deviationRate = BigDecimalUtils.subStractAndDividePrecent(currentClosePrice, movingAverage);

            deviationRates.add(deviationRate); // 保留两位小数
        }

        return deviationRates;
    }

    /**
     * 获取bias波段区域列表
     *
     * @param dataList
     * @return
     */
    public static List<BiasRegion> getBiasRegionList(List<StockDayData> dataList, int period, int longPeriod) {
        if (CollectionUtils.isEmpty(dataList)) {
            return null;
        }
        calculateBias(dataList, period, longPeriod);
        int beginIndex = 0;
        int limitIndex = 0;
        int beforeEndIndex = 0;
        Bias limit = null;

        List<BiasRegion> biasRegionList = new ArrayList<>();
        boolean beginFlag = false;
        BiasRegion biasRegion = null;
        //上涨区间
        for (int i = 0; i < dataList.size(); i++) {
            if (i - beforeEndIndex < 10) {
                continue;
            }
            StockDayData data = dataList.get(i);
            Bias bias = data.getBias();
            if (!beginFlag && isActive(bias, data) && BigDecimalUtils.isGt(data.getRate(), BigDecimal.ZERO)
                && isHighPostion(dataList, data)) {
                beginFlag = true;
                biasRegion = new BiasRegion();
                biasRegion.setOperateInLimitIndex(-1);
                biasRegion.setUpFlag(BooleanUtils.TRUE);
                biasRegion.setBegin(BeanConvertUtils.convert(bias, Bias.class));
                beginIndex = i;
                biasRegionList.add(biasRegion);
                limit = bias;
                continue;
            }
            if (beginFlag && isActive(bias, data)) {
                if (BigDecimalUtils.isGt(bias.getBias(), limit.getBias())) {
                    limit = bias;
                    limitIndex = i;
                }
                continue;
            }
            if (beginFlag && !isActive(bias, data)) {
                biasRegion.setEnd(BeanConvertUtils.convert(bias, Bias.class));
                biasRegion.setLimit(BeanConvertUtils.convert(bias, Bias.class));
                biasRegion.setDataList(dataList.subList(beginIndex, i).stream().map(StockDayData::getBias)
                    .collect(Collectors.toList()));
                biasRegion.setLength(dataList.size());
                biasRegion.setLimitIndex(limitIndex - beginIndex);
                biasRegion.setRegionValue(getRegionValue(biasRegion));
                biasRegion.setOperateInLimitIndex(0);
                //重置
                beginFlag = false;
                limit = null;
                beginIndex = 0;
                limitIndex = 0;
                beforeEndIndex = i;
                continue;
            }

        }
        //处理最新的区间
        if (CollectionUtils.isNotEmpty(biasRegionList)) {
            dealEndRegion(biasRegionList, dataList, beginIndex);
        }
        //下跌区间
        for (int i = 0; i < dataList.size(); i++) {
            StockDayData data = dataList.get(i);
            Bias bias = data.getBias();
            if (!beginFlag && isNegative(bias, data) && BigDecimalUtils.isLt(data.getRate(), BigDecimal.ZERO)
                && isLowPostion(dataList, data)) {
                beginFlag = true;
                biasRegion = new BiasRegion();
                biasRegion.setUpFlag(BooleanUtils.FALSE);
                biasRegion.setBegin(BeanConvertUtils.convert(bias, Bias.class));
                beginIndex = i;
                //biasRegionList.add(biasRegion);
                limit = bias;
                continue;
            }
            if (beginFlag && isNegative(bias, data)) {
                if (BigDecimalUtils.isLt(bias.getBias(), limit.getBias())) {
                    limit = bias;
                    limitIndex = i;
                }
                continue;
            }
            if (beginFlag && !isNegative(bias, data)) {
                biasRegion.setEnd(BeanConvertUtils.convert(bias, Bias.class));
                biasRegion.setLimit(BeanConvertUtils.convert(bias, Bias.class));
                biasRegion.setDataList(dataList.subList(beginIndex, i).stream().map(StockDayData::getBias)
                    .collect(Collectors.toList()));
                biasRegion.setLength(dataList.size());
                biasRegion.setLimitIndex(limitIndex - beginIndex);
                biasRegion.setRegionValue(getRegionValue(biasRegion));
                biasRegion.setOperateInLimitIndex(0);
                //重置
                beginFlag = false;
                limit = null;
                beginIndex = 0;
                limitIndex = 0;
                continue;
            }

        }
        //处理最新的区间
        if (CollectionUtils.isNotEmpty(biasRegionList)) {
            dealEndRegion(biasRegionList, dataList, beginIndex);
        }
        setRemark(biasRegionList, "bias");
        return biasRegionList;
    }

    private static void dealEndRegion(List<BiasRegion> biasRegionList, List<StockDayData> dataList, int beginIndex) {
        if (CollectionUtils.isNotEmpty(biasRegionList)) {
            BiasRegion lastBiasRegion = biasRegionList.get(biasRegionList.size() - 1);
            if (Objects.isNull(lastBiasRegion.getEnd())) {
                lastBiasRegion.setEnd(dataList.get(dataList.size() - 1).getBias());
                lastBiasRegion.setLimit(lastBiasRegion.getEnd());
                lastBiasRegion.setDataList(dataList.subList(beginIndex, dataList.size()).stream()
                    .map(StockDayData::getBias)
                    .collect(Collectors.toList()));
                lastBiasRegion.setLength(dataList.size());
                lastBiasRegion.setLimitIndex(lastBiasRegion.getDataList().size() - 1);
                lastBiasRegion.setRegionValue(getRegionValue(lastBiasRegion));
                lastBiasRegion.setOperateInLimitIndex(-1);
            }
        }
    }

    private static boolean isHighPostion(List<StockDayData> dataList, StockDayData data) {
        List<StockDayData> subList = StockUtils.getStockDayDataListBeforeDate(dataList, data.getDate(), 60);
        StockDayData limitData = StockUtils.getMaxPriceData(subList);
        int dateRage = WorkDayUtils.getWorkDayList(limitData.getDate(), data.getDate()).size();
        if (dateRage <= 3) {
            return true;
        }
        //前高在1个月前,当前已临近前高位置,也算是高位
        if (dateRage >= 20) {
            BigDecimal rate = BigDecimalUtils.subStractAndDividePrecent(limitData.getClosePrice(),
                data.getClosePrice());
            if (BigDecimalUtils.isLt(rate, BigDecimalUtils.three)) {
                return true;
            }
        }

        return false;
    }

    private static boolean isLowPostion(List<StockDayData> dataList, StockDayData data) {
        List<StockDayData> subList = StockUtils.getStockDayDataListBeforeDate(dataList, data.getDate(), 60);
        StockDayData limitData = StockUtils.getMinPriceData(subList);
        int dateRange = WorkDayUtils.getWorkDayList(limitData.getDate(), data.getDate()).size();
        if (dateRange <= 1) {
            return true;
        }
        //前低在1个月前,当前已临近前低位置,也算是高低
        if (dateRange >= 20) {
            BigDecimal rate = BigDecimalUtils.subStractAndDividePrecent(data.getClosePrice(),
                limitData.getClosePrice());
            if (BigDecimalUtils.isLt(rate, BigDecimalUtils.three)) {
                return true;
            }
        }

        return false;
    }

    private static BigDecimal getRegionValue(BiasRegion region) {
        return region.getDataList().stream().map(Bias::getBias).reduce(BigDecimal.ZERO, BigDecimal::add);
    }

    private static boolean isActive(Bias bias, StockDayData data) {
        return BigDecimalUtils.isGt(bias.getBias(), BigDecimalUtils.eight)
            && BigDecimalUtils.isGt(bias.getLongBias(), BigDecimalUtils.six)
            && BigDecimalUtils.isGt(data.getRate(), BigDecimalUtils.fuOnePointFive);
    }

    private static boolean isNegative(Bias bias, StockDayData data) {
        return BigDecimalUtils.isLt(bias.getBias(), BigDecimalUtils.fuEight)
            && BigDecimalUtils.isLt(bias.getLongBias(), BigDecimalUtils.fuEight)
            && BigDecimalUtils.isLt(data.getRate(), BigDecimalUtils.onePointFive);
    }

    public static void setRemark(List<BiasRegion> regionList, String remark) {
        for (BiasRegion region : regionList) {
            region.setRemark(StringUtils.isBlank(region.getRemark()) ? remark : region.getRemark() + "," + remark);
        }
    }

    private static void setOperateInLimit(List<BiasRegion> regionList, int operateInLimitIndex) {
        for (BiasRegion region : regionList) {
            region.setOperateInLimitIndex(operateInLimitIndex);
        }
    }

    /**
     * Bias指标类
     */
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class Bias {
        private String datetime;
        private BigDecimal bias;
        private BigDecimal longBias;
        private BigDecimal price;
    }

    /**
     * Bias区间
     */
    @Data
    public static class BiasRegion {
        private Bias begin;

        private Bias end;

        /**
         * 最高或最低值
         */
        private Bias limit;

        @JSONField(serialize = false)
        private List<Bias> dataList;

        private int length;
        /**
         * 最高或者最低的索引位置
         */
        private int limitIndex;
        /**
         * 波段值,用长度*最高柱表示
         */
        private BigDecimal regionValue;

        private String upFlag;

        private String remark;
        /**
         * 极限值日期索引后几天操作,0为当天操作
         */
        private int operateInLimitIndex;
    }

    public static void main(String[] args) {
        List<StockDayData> stockDayDataList = new ArrayList<>();

        // 模拟添加数据，可以替换为真实数据
        stockDayDataList.add(newStockDayData("0001", new BigDecimal("10"), new BigDecimal("10.5"), new BigDecimal("11"),
            new BigDecimal("9.5")));
        stockDayDataList.add(
            newStockDayData("0001", new BigDecimal("10.5"), new BigDecimal("11.0"), new BigDecimal("11.5"),
                new BigDecimal("10.0")));
        stockDayDataList.add(newStockDayData("0001", new BigDecimal("11"), new BigDecimal("11.5"), new BigDecimal("12"),
            new BigDecimal("10.5")));
        stockDayDataList.add(
            newStockDayData("0001", new BigDecimal("11.5"), new BigDecimal("12"), new BigDecimal("12.5"),
                new BigDecimal("11")));
        stockDayDataList.add(newStockDayData("0001", new BigDecimal("12"), new BigDecimal("12.5"), new BigDecimal("13"),
            new BigDecimal("11.5")));
        stockDayDataList.add(
            newStockDayData("0001", new BigDecimal("12.5"), new BigDecimal("13"), new BigDecimal("13.5"),
                new BigDecimal("12")));
        stockDayDataList.add(newStockDayData("0001", new BigDecimal("13"), new BigDecimal("13.5"), new BigDecimal("14"),
            new BigDecimal("12.5")));
        stockDayDataList.add(newStockDayData("0001", new BigDecimal("13"), new BigDecimal("13"), new BigDecimal("14"),
            new BigDecimal("12.5")));
        stockDayDataList.add(newStockDayData("0001", new BigDecimal("13"), new BigDecimal("12.5"), new BigDecimal("14"),
            new BigDecimal("12.5")));
        stockDayDataList.add(newStockDayData("0001", new BigDecimal("13"), new BigDecimal("12"), new BigDecimal("14"),
            new BigDecimal("12.5")));
        stockDayDataList.add(newStockDayData("0001", new BigDecimal("13"), new BigDecimal("12.5"), new BigDecimal("14"),
            new BigDecimal("12.5")));
        stockDayDataList.add(newStockDayData("0001", new BigDecimal("13"), new BigDecimal("12.5"), new BigDecimal("14"),
            new BigDecimal("12.5")));
        stockDayDataList.add(newStockDayData("0001", new BigDecimal("13"), new BigDecimal("11.5"), new BigDecimal("14"),
            new BigDecimal("12.5")));
        stockDayDataList.add(newStockDayData("0001", new BigDecimal("13"), new BigDecimal("11"), new BigDecimal("14"),
            new BigDecimal("12.5")));
        stockDayDataList.add(newStockDayData("0001", new BigDecimal("10"), new BigDecimal("10.5"), new BigDecimal("11"),
            new BigDecimal("9.5")));
        stockDayDataList.add(
            newStockDayData("0001", new BigDecimal("10.5"), new BigDecimal("11.0"), new BigDecimal("11.5"),
                new BigDecimal("10.0")));
        stockDayDataList.add(newStockDayData("0001", new BigDecimal("11"), new BigDecimal("11.5"), new BigDecimal("12"),
            new BigDecimal("10.5")));
        stockDayDataList.add(
            newStockDayData("0001", new BigDecimal("11.5"), new BigDecimal("12"), new BigDecimal("12.5"),
                new BigDecimal("11")));
        stockDayDataList.add(newStockDayData("0001", new BigDecimal("12"), new BigDecimal("12.5"), new BigDecimal("13"),
            new BigDecimal("11.5")));
        stockDayDataList.add(
            newStockDayData("0001", new BigDecimal("12.5"), new BigDecimal("13"), new BigDecimal("13.5"),
                new BigDecimal("12")));
        stockDayDataList.add(newStockDayData("0001", new BigDecimal("13"), new BigDecimal("13.5"), new BigDecimal("14"),
            new BigDecimal("12.5")));
        stockDayDataList.add(newStockDayData("0001", new BigDecimal("13"), new BigDecimal("13"), new BigDecimal("14"),
            new BigDecimal("12.5")));
        stockDayDataList.add(newStockDayData("0001", new BigDecimal("13"), new BigDecimal("12.5"), new BigDecimal("14"),
            new BigDecimal("12.5")));
        stockDayDataList.add(newStockDayData("0001", new BigDecimal("13"), new BigDecimal("12"), new BigDecimal("14"),
            new BigDecimal("12.5")));
        stockDayDataList.add(newStockDayData("0001", new BigDecimal("13"), new BigDecimal("12.5"), new BigDecimal("14"),
            new BigDecimal("12.5")));
        stockDayDataList.add(newStockDayData("0001", new BigDecimal("13"), new BigDecimal("12.5"), new BigDecimal("14"),
            new BigDecimal("12.5")));
        stockDayDataList.add(newStockDayData("0001", new BigDecimal("13"), new BigDecimal("11.5"), new BigDecimal("14"),
            new BigDecimal("12.5")));
        stockDayDataList.add(newStockDayData("0001", new BigDecimal("13"), new BigDecimal("11"), new BigDecimal("14"),
            new BigDecimal("12.5")));

        int period = 10;
        List<BigDecimal> biasList = calculateBias(stockDayDataList, period, 60);
        System.out.println(biasList);
    }

    private static StockDayData newStockDayData(String code, BigDecimal openPrice, BigDecimal closePrice,
        BigDecimal maxPrice, BigDecimal minPrice) {
        StockDayData stockDayData = new StockDayData();
        stockDayData.setCode(code);
        stockDayData.setOpenPrice(openPrice);
        stockDayData.setClosePrice(closePrice);
        stockDayData.setMaxPrice(maxPrice);
        stockDayData.setMinPrice(minPrice);
        return stockDayData;
    }

}

